package icasue.runninginvoke.task.proxy;

import com.alibaba.fastjson.JSONObject;
import icasue.base.utils.check.CommonCheck;
import com.alibaba.fastjson.JSON;
import icasue.runninginvoke.config.properties.TasksConfigProperties;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import icasue.runninginvoke.invoke.RunningInvoking;
import icasue.runninginvoke.task.redis.AsyncTaskOperate;

import java.util.Date;
import java.util.HashMap;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2020/11/11 下午5:31
 * @UpdateDate:
 * @Description:
 */
@Data
@NoArgsConstructor
@Slf4j
public class OnceTaskProxy extends TaskProxyHolder implements Runnable{

    private String taskId;
    private TasksConfigProperties.TaskDeclare.TaskStrategy taskStrategy;
    private RunningInvoking.InvokerBucket invokerBucket;
    private HttpEntity<Object> requestEntity;
    private Boolean portHasBeenMapping;
    private Boolean taskSecurity;
    private String host;
    private Integer port;

    public OnceTaskProxy(TasksConfigProperties.TaskDeclare metadata, AsyncTaskOperate asyncTaskOperate, RestTemplate restTemplateBalance, Boolean portHasBeenMapping, Boolean taskSecurity, String hostAndPort){
        super(asyncTaskOperate,restTemplateBalance);
        this.taskId = metadata.taskId;
        this.taskStrategy = metadata.strategy;
        this.invokerBucket = JSON.parseObject(JSON.toJSONString(metadata), RunningInvoking.InvokerBucket.class);
        this.portHasBeenMapping = portHasBeenMapping;
        this.taskSecurity = taskSecurity;
        if(!CommonCheck.nullStrings(hostAndPort)){
            String[] addressPort = hostAndPort.split(":");
            this.host = addressPort[0];
            this.port = Integer.valueOf(addressPort[1]);
            super.port = this.port;
        }
        this.init();
    }


    @Override
    protected void nextTrigger() {

    }

    @Override
    protected JSONObject executeBalance() {
        Boolean touchResult = asyncTaskOperate.touchExecute(taskId);
        if(touchResult) {
            String triggerTime = dateFormat.format(new Date());
            try {
                String uri = String.join(Strings.EMPTY, HTTP_PREFIX, COMMON_INVOKE_API);
                ResponseEntity<JSONObject> responseEntity =
                        restTemplate.exchange(uri, HttpMethod.POST, requestEntity, JSONObject.class);
                String balanceAddress = touchBalanceAddress(taskId);
                log.info("\n  OnceTaskProxy :: " +
                            "\n\t triggerTime: {} " +
                            "\n\t taskId: {} " +
                            "\n\t executeBalance: success " +
                            "\n\t nodePort: {} " +
                            "\n\t taskResult: {} \n",
                        triggerTime, taskId, balanceAddress, responseEntity.getBody());
                return responseEntity.getBody();
            }catch (Throwable e){
                String balanceAddress = touchBalanceAddress(taskId);
                log.info("\n  OnceTaskProxy :: " +
                            "\n\t triggerTime: {} " +
                            "\n\t taskId: {} " +
                            "\n\t executeBalance: error " +
                            "\n\t nodePort: {} " +
                            "\n\t details: {} \n",
                        triggerTime, taskId, balanceAddress, e.getMessage());
            }finally {
                asyncTaskOperate.destroyExecute(taskId);
            }
        }
        return null;
    }

    @Override
    protected void init() {
        HttpHeaders headers = new HttpHeaders();
        headers.add("taskId", this.taskId);
        headers.add("Content-Type", "application/json");
        headers.add("Accept", "application/json");
        headers.add("waitingResult", "false");
        headers.add("class", invokerBucket.getClassName());
        headers.add("method", invokerBucket.getMethodName());
        headers.add("description", invokerBucket.getFunctionDescription());
        HttpEntity<Object> requestEntity = new HttpEntity<Object>(this.invokerBucket, headers);
        this.requestEntity = requestEntity;
        executorService.submit(this::run);
    }

    @Override
    protected Object execute() {
        Boolean touchResult = asyncTaskOperate.touchExecute(taskId);
        if(touchResult) {
            String triggerTime = dateFormat.format(new Date());
            String localAddress = touchLocalAddress();
            try {
                HashMap<String, Object> identityMap = new HashMap<String, Object>(){{
                    put("host",host);
                    put("port",port);
                    put("timestamp", System.currentTimeMillis());
                    put("taskId", taskId);
                    put("class",invokerBucket.getClassName());
                    put("method",invokerBucket.getMethodName());
                    put("description",invokerBucket.getFunctionDescription());
                }};
                asyncTaskOperate.updateIdentity(taskId,JSON.toJSONString(identityMap));
                Object result = RunningInvoking.commonInvoke(this.invokerBucket,false);
                log.info("\n  OnceTaskProxy :: " +
                            "\n\t triggerTime: {} " +
                            "\n\t taskId: {} " +
                            "\n\t execute: success " +
                            "\n\t nodePort: {} " +
                            "\n\t taskResult: {} \n",
                        triggerTime, taskId, localAddress, result);
                return result;
            }catch (Throwable e){
                log.info("\n  OnceTaskProxy :: " +
                            "\n\t triggerTime: {} " +
                            "\n\t taskId: {} " +
                            "\n\t execute: error " +
                            "\n\t nodePort: {} " +
                            "\n\t details: {} \n",
                        triggerTime, taskId, localAddress, e.getMessage());
            }finally {
                asyncTaskOperate.destroyExecute(taskId);
            }
        }
        return null;
    }

    /**
     * 执行策略. all -> 服务实例local执行
     * 执行策略. single ->
     *               端口映射 -> balance -> success
     *               端口映射 -> balance -> failed
     *                          local -> end.
     *               未映射   -> local -> end.
     */
    @Override
    public void run() {
        if(taskStrategy == TasksConfigProperties.TaskDeclare.TaskStrategy.ALL){
            this.execute();
        }else {
            if(portHasBeenMapping){
                JSONObject balanceExecuteResult = this.executeBalance();
                // task security enabled? check task result and invoke fail back.
                if(taskSecurity && balanceExecuteResult == null){
                    // fail callback local execute.
                    this.execute();
                }
            }else {
                this.execute();
            }
        }
    }
}
